/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Text;
using System.Reflection;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using Microsoft.Scripting.Actions;
using Microsoft.Scripting.Utils;

namespace Microsoft.Scripting.Ast {
    //CONFORMING
    public sealed class NewExpression : Expression {
        private readonly ConstructorInfo _constructor;
        private readonly ReadOnlyCollection<Expression>/*!*/ _arguments;
        private readonly ReadOnlyCollection<MemberInfo> _members;

        internal NewExpression(Annotations annotations, Type type, ConstructorInfo constructor, ReadOnlyCollection<Expression>/*!*/ arguments, CallSiteBinder bindingInfo)
            : base(annotations, ExpressionType.New, type, bindingInfo) {
            if (IsBound) {
                RequiresBoundItems(arguments, "arguments");
            }
            _constructor = constructor;
            _arguments = arguments;
        }

        internal NewExpression(Annotations annotations, Type type, ConstructorInfo constructor, ReadOnlyCollection<Expression>/*!*/ arguments, CallSiteBinder bindingInfo, ReadOnlyCollection<MemberInfo> members)
            : this(annotations, type, constructor, arguments, bindingInfo) {

            _members = members;
        }

        public ConstructorInfo Constructor {
            get { return _constructor; }
        }

        public ReadOnlyCollection<Expression>/*!*/ Arguments {
            get { return _arguments; }
        }

        public ReadOnlyCollection<MemberInfo> Members {
            get { return _members; }
        }


        private static PropertyInfo GetPropertyNoThrow(MethodInfo method) {
            if (method == null) {
                return null;
            }
            Type type = method.DeclaringType;
            BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic;
            flags |= (method.IsStatic) ? BindingFlags.Static : BindingFlags.Instance;
            PropertyInfo[] props = type.GetProperties(flags);
            foreach (PropertyInfo pi in props) {
                if (pi.CanRead && method == pi.GetGetMethod(true)) {
                    return pi;
                }
                if (pi.CanWrite && method == pi.GetSetMethod(true)) {
                    return pi;
                }
            }
            return null;
        }

        internal override void BuildString(StringBuilder builder) {
            Type type = (_constructor == null) ? type = this.Type : _constructor.DeclaringType;
            builder.Append("new ");
            int n = _arguments.Count;
            builder.Append(type.Name);
            builder.Append("(");
            if (n > 0) {
                for (int i = 0; i < n; i++) {
                    if (i > 0) {
                        builder.Append(", ");
                    }
                    if (_members != null) {
                        PropertyInfo pi = null;
                        if (_members[i].MemberType == MemberTypes.Method && (pi = GetPropertyNoThrow((MethodInfo)_members[i])) != null) {
                            builder.Append(pi.Name);
                        } else {
                            builder.Append(_members[i].Name);
                        }
                        builder.Append(" = ");
                    }
                    _arguments[i].BuildString(builder);
                }
            }
            builder.Append(")");
        }
    }

    /// <summary>
    /// Factory methods.
    /// </summary>
    public partial class Expression {

        public static NewExpression New(ConstructorInfo/*!*/ constructor) {
            return New(constructor, new Expression[0]);
        }
        
        public static NewExpression New(ConstructorInfo/*!*/ constructor, params Expression/*!*/[]/*!*/ arguments) {
            return New(constructor, (IList<Expression>)arguments);
        }

        public static NewExpression New(ConstructorInfo/*!*/ constructor, IEnumerable<Expression/*!*/>/*!*/ arguments) {
            ContractUtils.RequiresNotNull(constructor, "constructor");
            ReadOnlyCollection<Expression> argumentList = CollectionUtils.ToReadOnlyCollection(arguments);

            ContractUtils.RequiresNotNullItems(argumentList, "arguments");
            ContractUtils.Requires(!constructor.DeclaringType.ContainsGenericParameters, "constructor", "Cannot instantiate an open generic type");

            ParameterInfo[] parameters = constructor.GetParameters();
            ValidateCallArguments(parameters, argumentList);

            return new NewExpression(Annotations.Empty, constructor.DeclaringType, constructor, argumentList, null);
        }

        public static NewExpression New(ConstructorInfo constructor, IEnumerable<Expression> arguments, IEnumerable<MemberInfo> members) {
            ContractUtils.RequiresNotNull(constructor, "constructor");
            ReadOnlyCollection<Expression> argumentList = CollectionUtils.ToReadOnlyCollection(arguments);

            ContractUtils.RequiresNotNullItems(argumentList, "arguments");
            ContractUtils.Requires(!constructor.DeclaringType.ContainsGenericParameters, "constructor", "Cannot instantiate an open generic type");

            ParameterInfo[] parameters = constructor.GetParameters();
            ValidateCallArguments(parameters, argumentList);

            return new NewExpression(Annotations.Empty, constructor.DeclaringType, constructor, argumentList, null, CollectionUtils.ToReadOnlyCollection(members));
        }

        public static NewExpression New(ConstructorInfo constructor, IEnumerable<Expression> arguments, params MemberInfo[] members) {
            return New(constructor, arguments, CollectionUtils.ToReadOnlyCollection(members));
        }

        public static NewExpression New(Type type) {
            ContractUtils.RequiresNotNull(type, "type");

            ReadOnlyCollection<Expression> noArgs = CollectionUtils.ToReadOnlyCollection<Expression>(new Expression[0]);

            if (type.IsValueType) {
                return new NewExpression(Annotations.Empty, type, null, noArgs, null);
            } else {
                ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
                ContractUtils.Requires(ci != null, "type", "type must have a parameterless constructor");
                return new NewExpression(Annotations.Empty, type, ci, noArgs, null);
            }
        }

        public static NewExpression New(Type result, CallSiteBinder bindingInfo, params Expression/*!*/[]/*!*/ arguments) {
            return New(result, bindingInfo, (IList<Expression>)arguments);
        }

        public static NewExpression New(Type result, CallSiteBinder bindingInfo, IList<Expression/*!*/>/*!*/ arguments) {
            ContractUtils.RequiresNotNull(bindingInfo, "bindingInfo");
            ContractUtils.RequiresNotNullItems(arguments, "arguments");
            return new NewExpression(Annotations.Empty, result, null, CollectionUtils.ToReadOnlyCollection(arguments), bindingInfo);
        }

        public static NewExpression SimpleNewHelper(ConstructorInfo/*!*/ constructor, params Expression/*!*/[]/*!*/ arguments) {
            ContractUtils.RequiresNotNull(constructor, "constructor");
            ContractUtils.RequiresNotNullItems(arguments, "arguments");

            ParameterInfo[] parameters = constructor.GetParameters();
            ContractUtils.Requires(arguments.Length == parameters.Length, "arguments", "Incorrect number of arguments");

            return New(constructor, ArgumentConvertHelper(arguments, parameters));
        }
    }
}
